/**
 * @file lv_obj_matrix.c
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "lv_obj_matrix_private.h"

#include "../../../lvgl/src/core/lv_obj_class_private.h"
#include "../../../lvgl/src/layouts/flex/lv_flex.h"
#include "../../../lvgl/src/display/lv_display.h"

#if LV_USE_OBJ_MATRIX

/*********************
 *      DEFINES
 *********************/
#define MY_CLASS        (&lv_obj_matrix_class)

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
uint16_t lv_obj_matrix_get_vcount(lv_obj_t * obj);
uint16_t lv_obj_matrix_get_hcount(lv_obj_t * obj);
uint32_t lv_obj_matrix_get_vspace(lv_obj_t * obj);
uint32_t lv_obj_matrix_get_hspace(lv_obj_t * obj);
static void event_handler(lv_event_t *e);
static void update_layout(lv_obj_t * obj);
static void lv_obj_matrix_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
static void lv_obj_matrix_event(const lv_obj_class_t * class_p, lv_event_t * e);

/**********************
 *  STATIC VARIABLES
 **********************/
const lv_obj_class_t lv_obj_matrix_class = {
    .event_cb = lv_obj_matrix_event,
    .base_class = &lv_obj_class,
    .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
    .width_def = LV_SIZE_CONTENT,
    .height_def = LV_SIZE_CONTENT,
    .instance_size = sizeof(lv_obj_matrix_t),
    .name = "lv_obj_matrix",
};

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

lv_obj_t * lv_obj_matrix_create(lv_obj_t * parent)
{
    LV_LOG_INFO("begin");
    lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
    lv_obj_class_init_obj(obj);
    lv_obj_remove_style_all(obj);
    lv_obj_set_size(obj, lv_pct(100), lv_pct(100));
    lv_obj_add_event_cb(obj, event_handler, LV_EVENT_SIZE_CHANGED, parent);
    lv_group_focus_obj(obj);    
    return obj;
}

void lv_obj_matrix_set_info(lv_obj_t * obj, const struct _lv_obj_matrix_info_t *info)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_obj_matrix_t * matrix = (lv_obj_matrix_t *)obj;
    lv_memcpy(&matrix->info, info, sizeof(lv_obj_matrix_info_t));
}

void lv_obj_matrix_update(lv_obj_t * obj)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    update_layout(obj);
}

void lv_obj_matrix_del_preprocessing(lv_obj_t * obj)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_obj_matrix_t * matrix = (lv_obj_matrix_t *)obj;
    delete_cell_cb_t delete_cell_cb = matrix->info.delete_cell_cb;
    if(delete_cell_cb == NULL) return;

    uint16_t vcount = lv_obj_matrix_get_vcount(obj);
    uint16_t hcount = lv_obj_matrix_get_hcount(obj);
    uint16_t valid_count = lv_obj_matrix_get_valid_count(obj);
    for(uint16_t v = 0; v < vcount; v++){
        for(uint16_t h = 0; h < hcount; h++){
            if((v * hcount + h) < valid_count) delete_cell_cb(obj, h, v);
        }
    }
}


void lv_obj_matrix_set_count(lv_obj_t * obj, uint16_t hcount, uint16_t vcount, uint16_t ccount, uint16_t cellsize_x, uint16_t cellsize_y)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_obj_matrix_t * matrix = (lv_obj_matrix_t *)obj;
    uint16_t org_count = lv_obj_get_child_count(obj);
    matrix->info.hcount = hcount;
    matrix->info.vcount = vcount;
    matrix->info.ccount = ccount;
    matrix->info.cellsize.x = cellsize_x;
    matrix->info.cellsize.y = cellsize_y;
    uint16_t count = lv_obj_matrix_get_valid_count(obj);
    uint16_t org_hcount = lv_obj_matrix_get_hcount(obj);

    if(org_count > count){
      for(; count < org_count; count++){
          if(matrix->info.event_cell_cb != NULL)
              matrix->info.delete_cell_cb(obj, count % org_hcount, count / org_hcount);
          lv_obj_delete(lv_obj_get_child(obj, count));
      }
    }
    /*else if(org_count < count)  Create in update_layout()*/
    update_layout(obj);
}

void lv_obj_matrix_set_hcount(lv_obj_t * obj, uint16_t hcount)
{
    if(hcount == 0) return;
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_obj_matrix_t * matrix = (lv_obj_matrix_t *)obj;
    if(matrix->info.hcount == 0) return;
    lv_obj_matrix_set_count(obj, hcount, matrix->info.vcount, matrix->info.ccount, matrix->info.cellsize.x, matrix->info.cellsize.y);
}

void lv_obj_matrix_set_vcount(lv_obj_t * obj, uint16_t vcount)
{
    if(vcount == 0) return;
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_obj_matrix_t * matrix = (lv_obj_matrix_t *)obj;
    if(matrix->info.vcount == 0) return;
    lv_obj_matrix_set_count(obj, matrix->info.hcount, vcount, matrix->info.ccount, matrix->info.cellsize.x, matrix->info.cellsize.y);
}

void lv_obj_matrix_set_ccount(lv_obj_t * obj, uint16_t ccount)
{
    if(ccount == 0) return;
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_obj_matrix_t * matrix = (lv_obj_matrix_t *)obj;
    if(matrix->info.hcount && matrix->info.vcount) return;
    lv_obj_matrix_set_count(obj, matrix->info.hcount, matrix->info.vcount, ccount, matrix->info.cellsize.x, matrix->info.cellsize.y);
}

void lv_obj_matrix_set_cellsize_x(lv_obj_t * obj, uint16_t cellsize_x)
{
    if(cellsize_x == 0) return;
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_obj_matrix_t * matrix = (lv_obj_matrix_t *)obj;
    if(matrix->info.cellsize.x == 0) return;
    lv_obj_matrix_set_count(obj, matrix->info.hcount, matrix->info.vcount, matrix->info.ccount, cellsize_x, matrix->info.cellsize.y);
}

void lv_obj_matrix_set_cellsize_y(lv_obj_t * obj, uint16_t cellsize_y)
{
    if(cellsize_y == 0) return;
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_obj_matrix_t * matrix = (lv_obj_matrix_t *)obj;
    if(matrix->info.cellsize.y == 0) return;
    lv_obj_matrix_set_count(obj, matrix->info.hcount, matrix->info.vcount, matrix->info.ccount, matrix->info.cellsize.x, cellsize_y);
}

void lv_obj_matrix_set_align(lv_obj_t * obj, lv_align_t align)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_obj_matrix_t * matrix = (lv_obj_matrix_t *)obj;
    matrix->info.align = align;
}

void lv_obj_matrix_set_margin(lv_obj_t * obj, const lv_obj_matrix_margin_t *margin)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_obj_matrix_t * matrix = (lv_obj_matrix_t *)obj;
    lv_memcpy(&matrix->info.margin, margin, sizeof(lv_obj_matrix_margin_t));
}


/**********************
 * GLOBAL PROTOTYPES (GETTER)
 **********************/

int16_t lv_obj_matrix_get_cell_id(lv_obj_t * obj, uint16_t hpos, uint16_t vpos)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    uint16_t id = vpos * lv_obj_matrix_get_hcount(obj) + hpos;
    //if(id >= lv_obj_matrix_get_valid_count(obj)) return -1;
    if(id >= lv_obj_get_child_count(obj)) return -1;
    return id;
}

lv_obj_t * lv_obj_matrix_get_cell_container(lv_obj_t * obj, uint16_t hpos, uint16_t vpos)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    uint16_t hcount = lv_obj_matrix_get_hcount(obj);
    return lv_obj_get_child(obj, vpos * hcount + hpos);
}

uint16_t lv_obj_matrix_get_vcount(lv_obj_t * obj)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_obj_matrix_t * matrix = (lv_obj_matrix_t *)obj;

    if(matrix->info.vcount) return matrix->info.vcount;
    //auto vcount
    if(!matrix->info.cellsize.y) return 1;
    if(matrix->info.ccount > 0){/*growth along the y-axis*/
      uint16_t hcount = lv_obj_matrix_get_hcount(obj);
      return (matrix->info.ccount + (hcount - 1)) / hcount;
    }
    else if(matrix->info.ccount < 0){/*growth along the x-axis*/
      if(matrix->info.hcount){/*ccount config invalid*/
        uint16_t ccount = 0 - matrix->info.ccount;
        return (ccount + (matrix->info.hcount - 1)) / matrix->info.hcount;
      }
      uint32_t margin = matrix->info.margin.top + matrix->info.margin.bottom;
      return (lv_obj_get_height(obj) - margin) / matrix->info.cellsize.y;
    }
    /*auto x-axis and y-axis: Not currently supported*/
    return 1;
}

uint16_t lv_obj_matrix_get_hcount(lv_obj_t * obj)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_obj_matrix_t * matrix = (lv_obj_matrix_t *)obj;
    if(matrix->info.hcount) return matrix->info.hcount;
    //auto hcount
    if(!matrix->info.cellsize.x) return 1;
    if(matrix->info.ccount < 0){/*growth along the x-axis*/
      uint16_t vcount = lv_obj_matrix_get_vcount(obj);
      uint16_t ccount = 0 - matrix->info.ccount;
      return (ccount + (vcount - 1)) / vcount;
    }
    else if(matrix->info.ccount > 0){/*growth along the y-axis*/
      if(matrix->info.vcount){/*ccount config invalid*/
        return (matrix->info.ccount + (matrix->info.vcount - 1)) / matrix->info.vcount;
      }
      uint32_t margin = matrix->info.margin.left + matrix->info.margin.right;
      return (lv_obj_get_width(obj) - margin) / matrix->info.cellsize.x;
    }
    /*auto x-axis and y-axis: Not currently supported*/
    return 1;
}

uint16_t lv_obj_matrix_get_count(lv_obj_t * obj)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_obj_matrix_t * matrix = (lv_obj_matrix_t *)obj;
   return matrix->info.vcount * matrix->info.hcount;
}

uint16_t lv_obj_matrix_get_valid_count(lv_obj_t * obj)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_obj_matrix_t * matrix = (lv_obj_matrix_t *)obj;
    if(matrix->info.vcount && matrix->info.hcount)
      return matrix->info.vcount * matrix->info.hcount;
    if(!matrix->info.cellsize.x && !matrix->info.cellsize.y) return 1;
    if(matrix->info.ccount == 0) return 1;
    if(matrix->info.ccount > 0) return matrix->info.ccount;
    return 0 - matrix->info.ccount;
}

uint32_t lv_obj_matrix_get_vspace(lv_obj_t * obj)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_obj_matrix_t * matrix = (lv_obj_matrix_t *)obj;
    uint32_t margin = matrix->info.margin.top + matrix->info.margin.bottom;
    uint32_t vspace = lv_obj_get_height(obj);
    if(vspace > margin){
      vspace -= margin;
      if(matrix->info.vcount > 0) vspace /= matrix->info.vcount;
      else if(matrix->info.cellsize.y) return matrix->info.cellsize.y;
    }
    else vspace = 0;
    if(vspace < matrix->info.cellsize.y) vspace = matrix->info.cellsize.y;
    return vspace;
}

uint32_t lv_obj_matrix_get_hspace(lv_obj_t * obj)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_obj_matrix_t * matrix = (lv_obj_matrix_t *)obj;
    uint32_t margin = matrix->info.margin.left + matrix->info.margin.right;
    uint32_t hspace = lv_obj_get_width(obj);
    if(hspace > margin){
      hspace -= margin;
      if(matrix->info.hcount > 0) hspace /= matrix->info.hcount;
      else if(matrix->info.cellsize.x) return matrix->info.cellsize.x;
    }
    else hspace = 0;
    if(hspace < matrix->info.cellsize.x) hspace = matrix->info.cellsize.x;
    return hspace;
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

static void event_handler(lv_event_t *e)
{
    lv_event_code_t code = lv_event_get_code(e);
    if(code != LV_EVENT_SIZE_CHANGED) return;
    lv_obj_t *obj = lv_event_get_target_obj(e);
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_obj_matrix_t * matrix = (lv_obj_matrix_t *)obj;
    if(matrix->info.event_cell_cb != NULL){
        matrix->info.event_cell_cb(obj, LV_EVENT_SIZE_CHANGED, 0 - lv_obj_matrix_get_hspace(obj), 0 - lv_obj_matrix_get_vspace(obj));
    }
    update_layout(obj);
}

static void update_layout(lv_obj_t * obj)
{
    lv_obj_update_layout(obj);
    uint16_t vcount = lv_obj_matrix_get_vcount(obj);
    uint16_t hcount = lv_obj_matrix_get_hcount(obj);
    uint32_t vspace = lv_obj_matrix_get_vspace(obj);
    uint32_t hspace = lv_obj_matrix_get_hspace(obj);
    lv_obj_matrix_t * matrix = (lv_obj_matrix_t *)obj;
    uint32_t cw, ch;
    if(matrix->info.cellsize.x > 0) cw = matrix->info.cellsize.x;
    else cw = hspace;
    if(matrix->info.cellsize.y > 0) ch = matrix->info.cellsize.y;
    else ch = vspace;
    uint16_t valid_count = lv_obj_matrix_get_valid_count(obj);
    lv_align_t align = matrix->info.align;
    if(align > LV_ALIGN_CENTER) align = LV_ALIGN_CENTER;
    uint8_t yalign = (align - 1) / 3; /*0top 1bottom 2mid*/
    int32_t ypos;
    if(yalign){
        ypos = lv_obj_get_height(obj) - vcount * vspace;
        if(yalign == 2) ypos /= 2;
        if(ypos < matrix->info.margin.top) ypos = matrix->info.margin.top;
    }
    else ypos = matrix->info.margin.top;
    uint8_t xalign; /*0left 1mid 2right*/
    if(align == LV_ALIGN_RIGHT_MID) xalign = 2;
    else if(align == LV_ALIGN_CENTER) xalign = 1;
    else xalign = (align - 1) % 3;
    for(uint16_t v = 0; v < vcount; v++){
        int32_t xpos;
        if(xalign){
            xpos  = lv_obj_get_width(obj) - hcount * hspace;
            if(xalign == 1) xpos /= 2;
            if(xpos < matrix->info.margin.left) xpos = matrix->info.margin.left;
        }
        else xpos = matrix->info.margin.left;
        for(uint16_t h = 0; h < hcount; h++){
            lv_obj_t * cell_container;
            uint16_t pos = v * hcount + h;
            if(pos >= valid_count) continue;
            cell_container = lv_obj_get_child(obj, pos);
            bool is_create;
            if(cell_container == NULL){
                cell_container = lv_obj_create(obj);
                lv_obj_remove_style_all(cell_container);
                is_create = true;
            }
            else is_create = false;
            lv_obj_set_pos(cell_container, xpos, ypos);
            lv_obj_set_size(cell_container, cw, ch);
            if(is_create){
                if(matrix->info.create_cell_cb != NULL)
                    matrix->info.create_cell_cb(obj, h, v);
            }
            else{
                if(matrix->info.event_cell_cb != NULL)
                  matrix->info.event_cell_cb(obj, LV_EVENT_SIZE_CHANGED, h, v);
            }
            xpos += hspace;
        }
        ypos += vspace;
    }
}

static void lv_obj_matrix_event(const lv_obj_class_t * class_p, lv_event_t * e)
{
    LV_UNUSED(class_p);
    lv_event_code_t code = lv_event_get_code(e);
    if(code == LV_EVENT_KEY){
        lv_obj_t * obj = lv_event_get_current_target(e);
        lv_obj_matrix_t * matrix = (lv_obj_matrix_t *)obj;
        if(matrix->info.event_cell_cb != NULL)
              matrix->info.event_cell_cb(obj, LV_EVENT_KEY, lv_event_get_key(e), -1);
        return;
    }
    /*Call the ancestor's event handler*/
    lv_obj_event_base(MY_CLASS, e);
}

#endif /*LV_USE_OBJ_MATRIX*/
